package spl.army;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;

import spl.utils.*;

public class ChiefOfStaff  implements Runnable {
	private static MissionHolder mainMissionHolder = new MissionHolder();
	private static List<Sergeant> sergeants = new ArrayList<Sergeant>();
	private static boolean mainThreadStarted = false;
	private static boolean mainThreadFinished = false;
	private static boolean stopRunning = false;
	
	private static GlobalLogger globalLogger = new GlobalLogger(); 
	
	private static Object notifier = new Object();
	
	/**
	 * @return true the main thread is finished
	 */
	public static boolean done(){
		return mainThreadStarted && mainThreadFinished;
	}
	
	/**
	 * @throws IllegalStateException is thrown when trying to reset uninitialized thread.
	 */
	public static void resetChiefOfStaff() throws IllegalStateException
	{
		if(mainThreadStarted && !mainThreadFinished)
			throw new IllegalStateException("main thread didn't finished");
		ChiefOfStaff.mainMissionHolder = new MissionHolder();
		ChiefOfStaff.sergeants = new ArrayList<Sergeant>();
		ChiefOfStaff.mainThreadStarted = false;
		ChiefOfStaff.mainThreadFinished = false;
	}
	
	/**
	 * @param sergeantsPropFile path on disk to the config file
	 * @throws ParseException when file's format is incorrect
	 * @throws FileNotFoundException when file is not found on disk
	 * @throws IOException on other IO error
	 */
	public static void initSergeantsFromPropFile(String sergeantsPropFile) throws ParseException, FileNotFoundException, IOException
	{
		FileProperties fp;
		fp = new FileProperties(sergeantsPropFile);
		String numberOfSergeantsStr = fp.getProperty("numberOfSergeants");
		if(null==numberOfSergeantsStr) {
			ChiefOfStaff.globalLogger.logger.severe("no attribute 'numberOfSergeants' in the given properties file");
			throw new ParseException("no attribute 'numberOfSergeants' in the given properties file", 0);
		}
		// Parse the number of sergeants and add them to the proper db.
		Integer numberOfSergeants = Integer.decode(numberOfSergeantsStr.trim());
		ChiefOfStaff.globalLogger.logger.finest("Found " + numberOfSergeants + " sergeants");
		
		for(int i=0; i<numberOfSergeants; i++){
			String name = fp.getProperty(i, "s", "Name");
			String numOfThreads = fp.getProperty(i, "s", "NumOfThreads");
			String maxMissions = fp.getProperty(i, "s", "MaxMissions");
			String skills = fp.getProperty(i, "s", "Skills");
			String maxWorkHours = fp.getProperty(i, "s", "WorkHours");
			String priorityOrder = fp.getProperty(i, "s", "PriorityOrder");
			Sergeant newSergeant = new Sergeant(name, maxMissions, skills, maxWorkHours, priorityOrder,numOfThreads);
			addSergeant(newSergeant);
		}		
	}
	
	/**
	 * @throws IllegalStateException when the thread is executed more than once.
	 */
	public void run() throws IllegalStateException{
		// Make sure chief is not started twice.
		if(ChiefOfStaff.mainThreadStarted) {
			throw new IllegalStateException("CheifOfStaff thread Should only start once");
		}
		
		ChiefOfStaff.mainThreadStarted = true;
		
		// Life cycle begins
		ChiefOfStaff.globalLogger.logger.finest("Chief started fighting");
		
		synchronized(notifier) {
			while(!Board.isAllDone() & !ChiefOfStaff.stopRunning){
				
				List<Mission> readyToExecuteMissions;
				readyToExecuteMissions = Board.getReadyToExecuteMissions();
				ChiefOfStaff.mainMissionHolder.addMissions(readyToExecuteMissions);
				
				Iterator<Sergeant> it = ChiefOfStaff.sergeants.iterator();
				Sergeant currentSergeant;
				
				GlobalLogger.getLogger().finest("Searching for missions assignment");
				// Iterate over sergeants and find missions to assign them
				while(it.hasNext()){
					currentSergeant = it.next();
					GlobalLogger.getLogger().finest("Searching for " + currentSergeant.getSergeantStatus());
					// Locking sergeant while assigning missions.
					synchronized(currentSergeant) {
						// Search for a mission that would suite the sergeant
						Mission candidateMission = ChiefOfStaff.mainMissionHolder.getMissionForSergeant(currentSergeant);
						// ableToWork checks if candidateMission == null
						while (currentSergeant.ableToWork(candidateMission)) {
					 		GlobalLogger.getLogger().finest("Found mission!");
					 		currentSergeant.giveMission(candidateMission);
					 		// Search for another one
					 		candidateMission = ChiefOfStaff.mainMissionHolder.getMissionForSergeant(currentSergeant);
					 	} // sergeant ableToWork
					} // Synched on sergeant
				} // next sergeant
				
				// All sergeants had been exhausted.
				try {
					GlobalLogger.getLogger().finest("Waiting for new missions or new sergeants");
					notifier.wait();
					
					
					GlobalLogger.getLogger().finest("Got updated from board");
				} catch (InterruptedException e) {
					ChiefOfStaff.mainThreadFinished = true; 
					ChiefOfStaff.gracefullyStopSergeants();
					return;
				}
			}
		}
		
		System.out.println("All missions are done");
		ChiefOfStaff.mainThreadFinished = true; 
		
		ChiefOfStaff.gracefullyStopSergeants();
	}
	
	/**
	 * notify the inner object of the board that makes the run()'s thread
	 * to get out of it's paused state back to running, therefore, to continue assigning new missions
	 */
	public static void updateFromBoard() {
		synchronized(notifier) {
			notifier.notifyAll();
		}
	}
	
	/**
	 * @param s is the sergeant that chief will enlist.
	 */
	public static void addSergeant(Sergeant s)
	{
		ChiefOfStaff.sergeants.add(s);
		ChiefOfStaff.globalLogger.logger.finest("Added Sergeant " + s.getName());
		
		// Notify run
		ChiefOfStaff.updateFromBoard();
		ChiefOfStaff.globalLogger.logger.finest("Chif was updated " + s.getName());
	}
	
	/**
	 * @return read only view of sergeants
	 */
	public static final List<Sergeant> getSergeants(){
		return Collections.unmodifiableList(ChiefOfStaff.sergeants);
	}
	
	/**
	 * sets off the running flag and shutting down sergeants.
	 * When all sergeants are stopped, notifying chief.
	 */
	public static void stop() {
		ChiefOfStaff.globalLogger.logger.info("Stopping chief");
		
		ChiefOfStaff.stopRunning = true;
		gracefullyStopSergeants();
	}
	
	/**
	 * Iterate over sergeant and set off it's runnig flag.
	 */
	private static void gracefullyStopSergeants() {
		ChiefOfStaff.globalLogger.logger.info("Stopping sergeants");
		Iterator<Sergeant> it = ChiefOfStaff.sergeants.iterator();

		while(it.hasNext()){
			Sergeant currentSergeant = it.next();
			currentSergeant.stop();
		}
		
		// Make sure chief is not sleeping:
		ChiefOfStaff.updateFromBoard();
	}
	
	/**
	 * Locks a sergeant at a time (and not all the simulation all together)
	 * and report it's status.
	 * We did not lock all the simulation because every sergeant is an atomic part
	 * of the simulation: missions given to a sergeant, are given to him only,
	 * so there is no way we would report missions in a false way.
	 * In other words - Every iteration has one correct status of the simulation.
	 */
	public static void reportSergeants() {
		Formatter formatter = new Formatter();
		// Observer thread: no other thread is to modify, at least, the known sergeants
		for (Sergeant s : ChiefOfStaff.getSergeants()) {
			
			int doneMissios = 0;
		
			// Now - Lock sergeant
			synchronized(s) {
				formatter.format("[+] %s\n", s.getSergeantStatus());
				GlobalLogger.getLogger().info("Locking sergeant " + s.toString());
				int undoneMissions=1; 
				for (Mission m : s.getAllAssignedMissions()) {
					if (m.getTimeLeft() != 0){
						formatter.format("\t%d ", undoneMissions);
						undoneMissions+=1;
					}
					else {
						formatter.format("\t[V] ");
						doneMissios += 1;
					}
					
					formatter.format("%s\n", m.toString());					
				}
				
				formatter.format("\tDone: %d\n", doneMissios);
				
				GlobalLogger.getLogger().info("Unlocking sergeant " + s.toString());
			}
		}
		
		System.out.print(formatter.toString());
	}
}
